/** Fichier implémentant la composition de formes.
 *
 * Ce fichier contient l'implémentation de la classe FormeComposee.
 *
 * @file FormeComposee.cpp
 * @author Jean-Marc Comby + Romain Dequesne
 * @author Johan "Solo" Luisier
 *
 * @date 23 Avril 2004
 */
#include "FormeComposee.hpp"

#include "Utils/Erreur.hpp"

#include <stdlib.h>

using namespace std;

/** Constructeur par défaut, qui met la Forme dans un état défini mais
 * inutilisable.
 *
 * Le nom est "Composée".
 *
 * @todo Il faut probablement virer ce constructeur.
 */
FormeComposee::FormeComposee()
    : Forme( "Composée" )
{
    op = unions;
    form[0] = 0;
    form[1] = 0;
    PseudoCentre = NullePart;
    PseudoRayon = 0.;
}

/** Constructeur standard.
 *
 * Ce constructeur compose deux formes avec l'opération voulue.
 *
 * @param[in] op1 opérateur de composition.
 * @param[in] forme1 pointeur sur la première Forme.
 * @param[in] forme2 pointeur sur la deuxième Forme.
 */
FormeComposee::FormeComposee(const operateur& op1, const Forme *forme1,
                             const Forme *forme2)
    : Forme( forme1 -> nom() + "-" + forme2 -> nom() )
{
    op = op1;
    form[0] = forme1;
    form[1] = forme2;
    Vecteur dist( forme1 -> pseudoCentre(), forme2 -> pseudoCentre() );
    PseudoCentre = forme1 -> pseudoCentre() + 0.5 * dist;
    PseudoRayon = forme1 -> pseudoRayon() + forme2 -> pseudoRayon() +
            sqrt( dist.norme() );
}

/** Destructeur, virtuel pour assurer une séquence de destruction correcte.
 *
 * Le destructeur ne fait rien de spécial.
 */
FormeComposee::~FormeComposee()
{}

/** Calcule l'intersection entre le rayon et la composition des deux formes.
 *
 * Ici il faut agir différemment selon l'opérateur choisi. Il faut
 * bien combiner les éventuelles intersections.
 *
 * @param[in] ray instance de rayon avec lequel il faut trouver l'intersection.
 *
 * @retval NullePart si l'intersection n'existe pas, @retval inter les
 * coordonnées du point d'intersection.
 */
Coord FormeComposee::trouveIntersection(Rayon const &ray) const
{
    bool tmp1(true),tmp2(true); // stock mes conditions
    Coord c1,c2;
    Vecteur n1, n2;
    switch ( op )
    {
    case difference:
        c1=form[0]->trouveIntersection(ray); // notre origine pour un nouveau rayon, enfin presque
        if (c1==NullePart)
            return NullePart;
        c2=form[1]->trouveIntersection(ray);
        if (c2==NullePart)
            return c1;
        n1 = Vecteur(ray.origine(),c1);
        n2 = Vecteur(ray.origine(),c2); // vecteur de l origine du rayon aux intersection
        if (n2.norme()<n1.norme())
        {
            double delta(0.00001); // va permettre de deplacer mon point d intersection de petit poil
            Vecteur v;
            v=delta*ray.direction(); // on va pouvoir avancer mon point dans la direction du rayon
            Coord nvelle_c(c1.x()+v.x(),c1.y()+v.y(),c1.z()+v.z()); // la nouvelle origine
            Rayon r(nvelle_c,ray.direction()); // relance un rayon avec une nvlle origine
            Coord inter1=form[0]->trouveIntersection(r);
            if (inter1==NullePart) // pas d interstion sur la form1
                tmp1=false;
            else
                tmp1=this->estSur(inter1); // intersection sur la form1

            Coord inter2=form[1]->trouveIntersection(r);
            if (inter2==NullePart)
                tmp2=false;
            else
                tmp2=this->estSur(inter2); // intersection sur la form2

            if (!tmp1 && !tmp2)
            {
                return NullePart;
            }
            if (tmp1 && !tmp2)
                return c1;
            if (!tmp1 && tmp2)
            {
                return NullePart;
            }
            if (tmp1 && tmp2)
            {
                Vecteur v1(inter1, r.origine()),v2(inter2, r.origine());
                if (v1.norme()<=v2.norme())
                    return inter1;
                else
                    return inter2;
            }
        }
        else
            return c1;
        break;
    default:
        Coord inter1=form[0]->trouveIntersection(ray);
        if (inter1==NullePart)
            tmp1=false;
        else
            tmp1=this->estSur(inter1); // intersection sur la form1

        Coord inter2=form[1]->trouveIntersection(ray);
        if (inter2==NullePart)
            tmp2=false;
        else
            tmp2=this->estSur(inter2); // intersection sur la form2

        if (!tmp1 && !tmp2)
        {
            return NullePart;
        }
        if (tmp1 && !tmp2)
            return inter1;
        if (!tmp1 && tmp2)
            return inter2;
        if (tmp1 && tmp2)
        {
            Vecteur v1(inter1, ray.origine()),v2(inter2, ray.origine());
            if (v1.norme()<=v2.norme())
                return inter1;
            else
                return inter2;
        }
    }
    return NullePart;
}

/** Calcule la valeur du champ de normale au point donné.
 *
 * Ici il faut agir différemment selon l'opérateur choisi.
 *
 * @param[in] pt point de la surface pour lequel on désire connaître la
 * normale.
 *
 * @return la normale à la surface par le point @c pt.
 */
VecteurU FormeComposee::normale(Coord const &pt) const
{
    VecteurU retval; // valeur de retour
    bool ison1=form[0]->estSur(pt);
    bool ison2=form[1]->estSur(pt);
    if (this->estSur(pt)) // on teste si le point est sur la forme
    {
        switch ( op )
        {
        case difference:
            if (ison1 && ison2) // Si le point est sur les 2 formes (cas rare, 2 possiblites max)
            {
                retval= - form[1]->normale(pt); // on renvoit la normale a la 2eme forme
                //retval *= -1.; // l opose
            }
            else
            {
                if (ison1 && !ison2) // Si le point est sur la 1er forme
                    retval=form[0]->normale(pt);
                else
                {
                    if (!ison1 && ison2) // Si le point est sur la 2eme forme
                    {
                        retval= - form[1]->normale(pt);
                        //retval*=-1;
                    }
                }
            }
            break;
        case unions:
        case inter:
            if (ison1 && ison2) // Si le point est sur les 2 formes
                retval=form[0]->normale(pt); // on renvoit la normale a la 1er forme
            else
            {
                if (ison1 && !ison2) // Si le point est sur la 1er forme
                    retval=form[0]->normale(pt);
                else
                {
                    if (!ison1 && ison2) // Si le point est sur la 2eme forme
                        retval=form[1]->normale(pt);
                }
            }
            break;
        default:
            cerr << "Impossible : valeur inconnue pour la composition" << endl;
        }
    }
    else // si le point n est pas sur la forme. N'arrive jamais dans le lancer de rayon
    {
        return VecteurU(9999,9998,9997);
    }
    return retval;
}

/** Permet de vérifier si un point est à @e l'intérieur de l'instance.
 *
 * Pour une \e intersection, il faut que le point se trouve dans les
 * deux formes.\n Pour une \e union, il suffit qu'il se trouve dans
 * une des deux.\n Pour une \e différence, il faut qu'il soit dans la
 * première mais pas dans la deuxième.
 *
 * @param[in] pt point à tester.
 *
 * @retval true si le point est bien dans le volume, @retval false sinon.
 */
bool FormeComposee::estDans(Coord const &pt) const
{
    switch (op)
    {
    case 0: // intersection
        if (form[0]->estDans(pt) && form[1]->estDans(pt))
            return true;
        else
            return false;
        break;
    case 1: // unions
        if (form[0]->estDans(pt) || form[1]->estDans(pt))
            return true;
        else
            return false;
        break;
    case 2: // difference
        if (form[0]->estDans(pt) && !form[1]->estDans(pt))
            return true;
        else
            return false;
        break;
    default: // dans ce cas, on quitte le prog en renvoyant une erreur
        Erreur err(true, 9, "Operateur pas conforme", "FormeComposee::estDans(Coord)");
        err.ecrire("Erreur.txt");
        cerr << "Crash. Consultez le log pour plus d info" << endl;
        exit(1);
    }
}

/** Permet de vérifier si un point est à la @e surface de l'instance.
 *
 * Pour une \e intersection, il faut que le point se trouve soit\n 1)
 * sur les deux surfaces, soit\n 2) sur la première surface et à
 * l'intérieur de la 2ème forme ou\n 3) l'inverse.\n Dans le cas d'une
 * \e union, il suffit que le point se trouve sur une des deux
 * surfaces.\n Pour une \e différence, il faut que le point se trouve
 * soit\n 1) sur la première surface mais pas dans la deuxième forme,
 * soit\n 2) sur la deuxième et dans la première, ou\n 3) sur les
 * deux.
 *
 * @param[in] pt point à tester.
 *
 * @retval true si le point est bien sur la surface, @retval false sinon.
 */
bool FormeComposee::estSur(Coord const &pt) const
{
    bool form1_on=form[0]->estSur(pt), form2_on=form[1]->estSur(pt);
    bool form1_in=form[0]->estDans(pt), form2_in=form[1]->estDans(pt);
    switch (op)
    {
    case inter:
        if ((form1_on && form2_in) ||
                (form2_on && form1_in) ||
                (form2_on && form1_on))
            return true;
        else
            return false;
        break;
    case unions:
        if ((form1_on || form2_on))
            return true;
        else
            return false;
        break;
    case difference:
        if ((form1_on && !form2_in) ||
                (form2_on && form1_in) ||
                (form2_on && form1_on))
            return true;
        else
            return false;
        break;
    default: // dans ce cas, on quitte le prog en renvoyant une erreur
        Erreur err(true, 9, "Operateur pas conforme", "FormeComposee::estSur(Coord)");
        err.ecrire("Erreur.txt");
        cerr << "Crash. Consultez le log pour plus d info" << endl;
        exit(1);
    }
}

/** Méthode permettant d'appliquer une texture sur la composition de la forme.
 *
 * On controle uniquement si le point est sur la première ou la deuxième forme,
 * et on délègue le travaille à la forme concernée.
 *
 * @param[in] pt point de la surface à mapper.
 * @param[out] i coordonnée x du pixel de l'image à plaquer au point @c pt.
 * @param[out] j coordonnée y du pixel de l'image à plaquer au point @c pt.
 * @param[in] frequence fréquence de répétition de la texture.
 * @param[in] Hauteur hauteur (en pixels) de l'image à mapper.
 * @param[in] Largeur largeur (en pixels) de l'image à mapper.
 */
void FormeComposee::mapping(const Coord &pt, int &i, int &j,
                            const int &frequence, const int &Hauteur,
                            const int &Largeur) const
{
    if ( form[ 0 ]-> estSur( pt ) )
        form[ 0 ] -> mapping( pt, i, j, frequence, Hauteur, Largeur );
    else
        form[ 1 ] -> mapping( pt, i, j, frequence, Hauteur, Largeur );
}

/** Méthode affichant les paramètres de l'instance.
 *
 * Cette méthode affiche la position du PseudoCentre et le PseudoRayon.
 *
 * @param[in] os référence sur le flot de sortie à utiliser.
 *
 * @return la référence sur le flot de sortie utilisé.
 */
ostream& FormeComposee::affiche(ostream& os) const
{
    os << "Forme : " << nom() << endl << "\t\"Centre\" : "
       << PseudoCentre << endl << "\t\"Rayon\" : " << PseudoRayon << endl;

    return os;
}

/** Méthode calculant les coordonnées intrinsèques d'un point de la surface.
 *
 * Le travail est délégué à la forme à laquelle le point appartient.
 *
 * @param[in] pt point de la surface.
 *
 * @retval NullePart si le point n'est pas sur la surface, @retval local les
 * coordonnées réduites sur la forme concernée.
 */
Coord FormeComposee::intrinseque( const Coord& pt ) const
{
    if ( ! estSur( pt ) )
        return NullePart;

    if ( form[ 0 ] -> estSur ( pt ) )
        return form[ 0 ] -> intrinseque( pt );
    else
        return form[ 1 ] -> intrinseque( pt );
}
